#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#ifdef S3C2440
#include <mach/gpio-samsung.h>
#else
#include <cfg_type.h>
#endif

#define LED_NR	4

static int led_pins[] = {
#ifdef S3C2440
	S3C2410_GPB(5),
	S3C2410_GPB(6),
	S3C2410_GPB(7),
	S3C2410_GPB(8),
#else
	PAD_GPIO_B + 26,
	PAD_GPIO_C + 11,
	PAD_GPIO_C + 7,
	PAD_GPIO_C + 12,
#endif
};

static dev_t led_major;
static struct class *led_class;

struct led_priv {
	dev_t device_id;
	int gpio_pin;
	int status;
	struct cdev led_cdev;
	struct device *led_device;
} led_dat[LED_NR];

static int led_open(struct inode *inode, struct file *filp)
{
	struct led_priv *dat;

	dat = container_of(inode->i_cdev, struct led_priv, led_cdev);
	filp->private_data = (void *)dat;

	return 0;
}

ssize_t led_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
	int val;
	int ret;
	char cmd[8];
	struct led_priv *led_dat;

	ret = copy_from_user(cmd, buf, 1);
	if (ret < 0) {
		printk("copy_from_user failed.\n");
		ret = -EINVAL;
	}

	led_dat = (struct led_priv *)filp->private_data;

	val = !(cmd[0] - '0');
	gpio_direction_output(led_dat->gpio_pin, val);

	ret = size;

	return ret;
}

static struct file_operations led_ops = {
	.open	= led_open,
	.write	= led_write,
};

static int __init led_multi_init(void)
{
	int ret;
	int i;

	ret = alloc_chrdev_region(&led_major, 0, LED_NR, "led_dev");
	if (ret < 0) {
		printk("led is register_chrdev_region error.\n");
		goto err_1;
	}

	led_class = class_create(THIS_MODULE, "led_class");
	if (IS_ERR(led_class)) {
		printk("led class_create error.\n");
		goto err_2;
	}

	for (i = 0; i < LED_NR; i++) {
		led_dat[i].gpio_pin = led_pins[i];
		led_dat[i].device_id = led_major + i;
		cdev_init(&led_dat[i].led_cdev, &led_ops);
		ret = cdev_add(&led_dat[i].led_cdev, led_dat[i].device_id, 1);
		if (ret < 0) {
			printk("Cdev add error.\n");
		}
		device_create(led_class, NULL, led_dat[i].device_id, NULL, "led%d", i);

		ret = gpio_request(led_pins[i], "LED");
		if (ret) {
			printk("%s: request GPIO %d for LED failed\n", "leds", led_dat[i].gpio_pin);
		}
		gpio_direction_output(led_dat[i].gpio_pin, 0);
		msleep(20);
		gpio_direction_output(led_dat[i].gpio_pin, 1);
	}

	return 0;

err_2:
	unregister_chrdev_region(led_major, LED_NR);
err_1:
	return ret;
}

static void __exit led_multi_exit(void)
{
	int i;

	for (i = LED_NR - 1; i >= 0; i--) {
		gpio_direction_output(led_dat[i].gpio_pin, 0);
		msleep(20);
		gpio_direction_output(led_dat[i].gpio_pin, 1);
		gpio_free(led_dat[i].gpio_pin);

		device_destroy(led_class, led_dat[i].device_id);
		cdev_del(&led_dat[i].led_cdev);
	}

	class_destroy(led_class);
	unregister_chrdev_region(led_major, LED_NR);
}

module_init(led_multi_init);
module_exit(led_multi_exit);
MODULE_AUTHOR("Yuyaowen");
MODULE_LICENSE("GPL");
